/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "xdrbase.h"
#include "channel.h"
#include "common.h"
#include "quickpool.h"
#include "lib.h"

#define exit(a)         _exit(a)

#define EEXECNAME "uexec"
#define EGROUPNAME "ugroup"

/**
 * @brief       create pipe to run command and get output of the command.
 *
 * @param[out]  ed            #1: put output of command in its data
 * @param[in]   argv          #2: command and its arguments
 * @param[in]   uid           #3: use this user to run the command
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: failed
 */
int
run_pipe(struct lenData *ed, char **argv, uid_t uid)
{
    int pipefd[2];
    if (pipe(pipefd) < 0) {
        log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "pipe");
        return -1;
    }

    pid_t pid;
    pid = fork();
    if (0 > pid) {

        log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "fork");
        close(pipefd[0]);
        close(pipefd[1]);
        return -1;
    } else if (0 == pid) {

        close(pipefd[0]);
        dup2(pipefd[1], 1);

        if (0 > setuid(uid)) {
            log_message(ERR, NOCLASS, "%s: setuid(%d) failed due to %m", __func__, uid);
        }

        execvp(argv[0], argv);
        log_message(ERR, NOCLASS, "%s: execvp(%s) failed due to %m", __func__, argv[0]);
        exit(-1);
    }

    close(pipefd[1]);

    ed->len = 0;
    ed->data = NULL;

    char *buf;
    buf = (char *)calloc(MAXMESSAGELENGTH + 1, sizeof(char));
    if (NULL == buf) {
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__,
                    "calloc", MAXMESSAGELENGTH+1);
	goto errorReturn;
    }

    char *sp;
    int cc, size;
    for (size=MAXMESSAGELENGTH, ed->len=0, sp=buf;
	 (cc = read(pipefd[0], sp, size));) {

        if (0 > cc) {

            if (errno == EINTR) {
		continue;
            }

	    log_message(ERR, NOCLASS, "%s: <%s> read(%d): %m", __func__, argv[0], size);
	    break;
	}

	ed->len += cc;
	sp += cc;
	size -= cc;
        if (0 == size) {

            sp = (char *)realloc(buf, ed->len + MAXMESSAGELENGTH + 1);
	    if (NULL == sp) {
                log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "realloc");
		free(buf);
		goto errorReturn;
	    }

	    buf = sp;
	    sp = buf + ed->len;
	    size = MAXMESSAGELENGTH;
	}
    }

    close(pipefd[0]);
    ed->data = buf;
    ed->data[ed->len] ='\0';

    int status;
    while (waitpid(pid, &status, 0) == -1 && errno == EINTR);

    char *abortVal;
    abortVal = getenv("QP_SUBMIT_ABORT");
    if (NULL != abortVal) {
	if ((WIFEXITED(status) && WEXITSTATUS(status) == atoi(abortVal)) || WIFSIGNALED(status)) {
	    FREEUP(ed->data);
	    ed->len = 0;
	    return -1;
	}
    }

    if (0 == ed->len) {
	FREEUP(ed->data);
    }

    return 0;

errorReturn:
    close(pipefd[0]);
    kill(pid, SIGKILL);
    while (waitpid(pid, 0, 0) == -1 && errno == EINTR);

    ed->len = 0;
    ed->data = NULL;

    return -1;
} // end function run_pipe

/**
 * @brief       run uexec command and get its output.
 *
 * @param[in]   option        #1: command options
 * @param[in]   jobid         #2: job ID
 * @param[out]  eexec         #3: output of uexec command
 * @param[in]   path          #4: fullpath of uexec command
 *
 * @retval      0             #1: succeed
 * @retval      -1            #2: failed
 */
int
run_exec(char *option, JOBID_T jobid, struct lenData *eexec, char *path)
{
    unsigned int cc;

    bool_t isRenew=FALSE;
    if (0 == strcmp(option, "-r")) {
        isRenew = TRUE;
    }

    char eexecPath[PATH_MAX+1];
    if (isRenew) {
        if ('\0' == path[0]) {
            strncpy(eexecPath, EEXECNAME, PATH_MAX);
        } else {
            snprintf(eexecPath, PATH_MAX, "%s/%s", path, EEXECNAME);
        }
    } else {
        snprintf(eexecPath, PATH_MAX, "%s/%s", baseParams[BASE_SERVERDIR].string_value, EEXECNAME);
    }

    char strJobId[32];
    log_message(DEBUG, TRACER, "%s: uexec path, option and data length of job/task <%s> are <%s>, <%s>.",
                __func__, jobid_2string(jobid, strJobId), eexecPath, option);

    struct stat sbuf;
    if (0 > stat(eexecPath, &sbuf)) {

        if (errno == ENOENT) {
            return 0;
        }

        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "stat", eexecPath);
        return -1;
    }

    int i = 0;

    char *myargv[3];
    myargv[i++] = eexecPath;
    if (0 == strcmp(option, "-r")) {
        myargv[i++] = (char *)"-r";
    }

    myargv[i] = NULL;

    int pipefd[2];
    if (0 > pipe(pipefd)) {
        log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "pipe");
	return -1;
    }

    int pid;
    pid = fork();
    if (0 > pid) {
        log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "fork");
	close(pipefd[0]);
	close(pipefd[1]);
	return -1;
    } else if (0 == pid) {

	char *user;
        uid_t uid;
        struct passwd *pw;

        user = getenv("QPUSER");
        if (NULL != user) {
            if (0 > get_uid(user, &uid)) {
                log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "get_uid", user);
                exit(-1);
            }
        } else {
            user = getenv("USER");
            pw = get_user_byname(user);
            if (NULL == pw) {
                log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "get_user_byname", user);
                exit(-1);
            }
            uid = pw->pw_uid;
        }

        if (0 > setuid(uid)) {
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "setuid", uid);
            exit (-1);
        }

        if (0 > setpgid(0, getpid())) {
            log_message(ERR, NOCLASS, STR_FUNC_FAIL_M, __func__, "setpgid");
            exit (-1);
        }

        for (i = 1; i < NSIG; i++) {
            Signal_(i, SIG_DFL);
        }

        alarm(0);

        close(pipefd[1]);
        if (dup2(pipefd[0], 0) == -1) {
            log_message(ERR, NOCLASS, STR_FUNC_FAIL_M,  __func__, "dup2(stdin,0)");
        }

	for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) {
            close(i);
	}

        execvp(myargv[0], myargv);
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "execvp", myargv[0]);

        exit(-1);
    }

    close(pipefd[0]);

    if (0 < eexec->len) {

        cc = gf_chan_write(pipefd[1], eexec->data, eexec->len, 10);
        if (cc != eexec->len) {
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "gf_chan_write", eexec->len);
	}
    }

    close(pipefd[1]);

    while (waitpid(pid, NULL, 0) < 0 && errno == EINTR);

    return 0;
} // end function run_exec

/**
 * @brief       run ugroup command and get its output.
 *
 * @param[in]   type          #1: command options
 * @param[in]   gname         #2: input group name
 *
 * @retval      not NULL      #1: succeed
 * @retval      NULL          #2: failed
 */
char *
run_group(const char *option, char *gname)
{
    char egroupPath[PATH_MAX+1];
    snprintf(egroupPath, PATH_MAX, "%s/%s", baseParams[BASE_SERVERDIR].string_value, EGROUPNAME);

    char *argv[4];
    argv[0] = egroupPath;
    argv[1] = (char *)option;
    argv[2] = gname;
    argv[3] = NULL;

    uid_t uid;
    uid = getuid();

    char *strMasterUid;
    strMasterUid = getenv("QP_MAINADMINID");
    if (0 == uid && NULL != strMasterUid) {
	uid = atoi(strMasterUid);
    }

    struct stat sbuf;
    if (0 > stat(egroupPath, &sbuf)) {

        if (errno == ENOENT) {
            return NULL;
        }

        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "stat", egroupPath);
	return NULL;
    }

    struct lenData ed;
    if (0 > run_pipe(&ed, argv, uid)) {
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "run_pipe", egroupPath);
	return NULL;
    }

    if (0 < ed.len) {
	ed.data[ed.len] = '\0';
	return ed.data;
    }

    return NULL;
} // end function run_group
